/*******************************************************************************
 * Copyright 2011 Mauro Luigi Drago (drago.luigi@gmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package com.bytenose.extrautils.patterns.dispatcher;

import java.lang.reflect.Method;

import com.bytenose.extrautils.annotations.NonNullable;
import com.bytenose.extrautils.annotations.UtilityClass;

import com.google.common.base.*;

/**
 * A utility class defining filters for methods based on their declaring type.
 * 
 * @author Mauro Luigi Drago
 * 
 */
@UtilityClass
public class DeclaringTypeFilters {

	/**
	 * Private constructor to avoid instantiation.
	 */
	private DeclaringTypeFilters() {
	}

	/**
	 * A filter matching methods declared by an interface.
	 * 
	 * @author Mauro Luigi Drago
	 * 
	 */
	private static class InterfaceDeclaredFilter implements Predicate<Method> {
		/**
		 * Creates a new {@link InterfaceDeclaredFilter}.
		 */
		private InterfaceDeclaredFilter() {
		}

		@Override
		public boolean apply(Method m) {
			return m.getDeclaringClass().isInterface();
		}
	}

	/**
	 * Creates a new filter matching methods declared by an interface.
	 * 
	 * @return the filter.
	 */
	@NonNullable
	public static Predicate<Method> getInterfaceDeclaredFilter() {
		return new InterfaceDeclaredFilter();
	}

	/**
	 * A filter matching methods declared by a class.
	 * 
	 * @author Mauro Luigi Drago
	 * 
	 */
	private static class ClassDeclaredFilter implements Predicate<Method> {
		/**
		 * Creates a new {@link ClassDeclaredFilter}.
		 */
		private ClassDeclaredFilter() {
		}

		@Override
		public boolean apply(Method m) {
			return !(m.getDeclaringClass().isInterface());
		}
	}

	/**
	 * Creates a new filter matching methods declared by a class.
	 * 
	 * @return the filter.
	 */
	@NonNullable
	public static Predicate<Method> getClassDeclaredFilter() {
		return new ClassDeclaredFilter();
	}

	/**
	 * A filter matching methods whose declaring type is of a specified type.
	 * 
	 * @author Mauro Luigi Drago
	 * 
	 */
	private static class DeclaringTypeFilter implements Predicate<Method> {
		/** The type to which the declaring type must conform */
		private final Class<?> requiredType;

		/**
		 * A flag indicating if the declaring type must be exactly the
		 * requiredType
		 */
		private final boolean exactMatch;

		/**
		 * Creates a new {@link DeclaringTypeFilter}.
		 * 
		 * @param requiredType
		 *            the type to which the declaring type must conform, cannot
		 *            be null.
		 * @param exactMatch
		 *            a flag indicating if the the declaring type must be
		 *            exactly the {@code requiredType}.
		 * @throws IllegalArgumentException
		 *             if some of the {@link NonNullable} arguments are null.
		 */
		private DeclaringTypeFilter(@NonNullable Class<?> requiredType,
				boolean exactMatch) throws IllegalArgumentException {
			if (requiredType == null)
				throw new IllegalArgumentException("requiredType");
			this.requiredType = requiredType;
			this.exactMatch = exactMatch;
		}

		@Override
		public boolean apply(Method m) {
			if (exactMatch)
				return requiredType.equals(m.getDeclaringClass());
			else
				return requiredType.isAssignableFrom(m.getDeclaringClass());
		}
	}

	/**
	 * Creates a new filter matching methods declared by a specified type. The
	 * filtering will be performed by checking that the type declaring a method
	 * is the same type specified by the {@code requiredType} parameter.
	 * 
	 * @param requiredType
	 *            the type required to declare the methods.
	 * @return the filter.
	 * @throws IllegalArgumentException
	 *             if some of the {@link NonNullable} arguments are null.
	 */
	@NonNullable
	public static Predicate<Method> getClassDeclaredFilter(
			@NonNullable Class<?> requiredType) {
		return new DeclaringTypeFilter(requiredType, true);
	}

	/**
	 * Creates a new filter matching methods declared by a specified type.
	 * 
	 * @param declaringType
	 *            the type declaring the methods.
	 * @param exactMatch
	 *            a flag indicating if the the declaring type must be exactly
	 *            the {@code requiredType}. If false, class conformance will be
	 *            used to perform the check.
	 * @return the filter.
	 * @throws IllegalArgumentException
	 *             if some of the {@link NonNullable} arguments are null.
	 */
	@NonNullable
	public static Predicate<Method> getClassDeclaredFilter(
			@NonNullable Class<?> declaringType, boolean exactMatch) {
		return new DeclaringTypeFilter(declaringType, exactMatch);
	}
}
